﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Reflection;

namespace PerformanceTimers
{
    /// <summary>
    /// Maintains pool of class instances.
    /// </summary>
    /// <typeparam name="T">
    /// The type of object to store. It must define a parameterless constructor, 
    /// and may implement <see cref="IResetable"/>.</typeparam>
    public class Pool<T> where T : class, new()
    {
        static Pool<T> instance;
        /// <summary>
        /// Gets the static instance.
        /// </summary>
        /// <value>The instance.</value>
        public static Pool<T> Instance
        {
            get
            {
                if (instance == null)
                {
                    var p = new Pool<T>();
                    Interlocked.CompareExchange(ref instance, p, null);
                }

                return instance;
            }
        }

        Stack<T> items;
        bool isResetableType;

        /// <summary>
        /// Initializes a new instance of the <see cref="Pool&lt;T&gt;"/> class.
        /// </summary>
        public Pool()
            : this(0)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Pool&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="initialCapacity">The initial number of elements contained within the <see cref="Pool&lt;T&gt;"/>.</param>
        public Pool(int initialCapacity)
        {
            isResetableType = TypeIsResetable();
            items = new Stack<T>(initialCapacity);
            for (int i = 0; i < initialCapacity; i++)
                items.Push(new T());
        }

        private static bool TypeIsResetable()
        {
            var type = typeof(T);
            var interfaces = type.GetInterfaces();
            for (int i = 0; i < interfaces.Length; i++)
            {
                if (interfaces[i].AssemblyQualifiedName == type.AssemblyQualifiedName)
                    return true;
            }

            return false;
        }

        /// <summary>
        /// Gets an instance of <typeparamref name="T"/> from the <see cref="Pool&lt;T&gt;"/>
        /// </summary>
        /// <returns>An instance of <typeparamref name="T"/>.</returns>
        public T Get()
        {
            if (items.Count > 0)
            {
                T item = items.Pop();
                if (isResetableType)
                    (item as IResetable).Reset();
                return item;
            }

            System.Diagnostics.Debug.WriteLine(DateTime.Now + " creating item");
            return new T();
        }

        /// <summary>
        /// Returns the specified item to the <see cref="Pool&lt;T&gt;"/>.
        /// </summary>
        /// <param name="item">The item to be returned.</param>
        public void Return(T item)
        {
            items.Push(item);
        }
    }

    /// <summary>
    /// Maintains a pool of class instances.
    /// </summary>
    public class Pool
    {
        Dictionary<Type, Stack<object>> items;

        /// <summary>
        /// Initializes a new instance of the <see cref="Pool"/> class.
        /// </summary>
        public Pool()
        {
            items = new Dictionary<Type, Stack<object>>();
        }

        /// <summary>
        /// Gets an instance from the <see cref="Pool"/>.
        /// </summary>
        /// <typeparam name="T">The type of object to store. It must define a parameterless constructor.</typeparam>
        /// <returns>An instance of <typeparamref name="T"/>.</returns>
        public T Get<T>() where T : class, new()
        {
            Stack<object> stack = GetStack(typeof(T));

            if (stack.Count > 0)
                return stack.Pop() as T;
            else
                return new T();
        }

        /// <summary>
        /// Returns the specified item to the <see cref="Pool"/>.
        /// </summary>
        /// <param name="item">The item to be returned.</param>
        public void Return(object item)
        {
            Stack<object> stack = GetStack(item.GetType());
            stack.Push(item);
        }

        private Stack<object> GetStack(Type type)
        {
            Stack<object> stack;
            if (!items.TryGetValue(type, out stack))
            {
                stack = new Stack<object>();
                items.Add(type, stack);
            }

            return stack;
        }
    }

    /// <summary>
    /// An interface defining that the object can be reset to its initial state.
    /// </summary>
    public interface IResetable
    {
        /// <summary>
        /// Resets the object to its initial state.
        /// </summary>
        void Reset();
    }
}
